package com.back.network.netty.handlers;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.Resource;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.back.network.netty.ProxyChannelManager;
import com.back.network.netty.constant.Constants;
import com.back.network.netty.event.TransferEvent;
import com.back.network.netty.http.MyHttpRequestEncoder;
import com.back.network.netty.protocol.ProxyMessage;
import com.back.network.web.service.UserService;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOption;
import io.netty.handler.codec.http.FullHttpRequest;

/**
 * 由于UserChannelHandler存在局部变量messages,并发时会出现数据错乱问题。因此需要多实例。 @Scope("prototype")
 * + ObjectFactory 来产生多实例效果
 */
@Component
@Sharable
@Scope("prototype")
public class UserChannelHandler extends ChannelInboundHandlerAdapter {

	@Resource
	UserService service;

	private static AtomicLong userIdProducer = new AtomicLong(0);

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		ctx.channel().attr(Constants.IS_CONNECT).set(true);
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object object) throws Exception {
		Channel userChannel = ctx.channel();
		FullHttpRequest request = (FullHttpRequest) object;
		if (userChannel.attr(Constants.IS_CONNECT).get()) {
			userChannel.attr(Constants.USER_REQUEST_DATA).set(request);
			connect(ctx, request.headers().get("Host"));
			userChannel.attr(Constants.IS_CONNECT).set(false);
			return;
		}
		httpRequestHandler(ctx, request);
	}

	public void httpRequestHandler(ChannelHandlerContext ctx, Object request) throws Exception {
		// 通知代理客户端
		Channel userChannel = ctx.channel();
		List<Object> list = new ArrayList<>();
		new MyHttpRequestEncoder().encode(ctx, request, list);
		Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
		for (Object object : list) {
			ByteBuf buf = (ByteBuf) object;
			byte[] bytes = new byte[buf.readableBytes()];
			buf.readBytes(bytes);
			if (proxyChannel == null) {
				// 该端口还没有代理客户端
				ctx.channel().close();
			} else {
				String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
				ProxyMessage proxyMessage = new ProxyMessage();
				proxyMessage.setType(ProxyMessage.P_TYPE_TRANSFER);
				proxyMessage.setUri(userId);
				proxyMessage.setData(bytes);
				proxyChannel.writeAndFlush(proxyMessage);
			}

		}
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		// 通知代理客户端
		Channel userChannel = ctx.channel();
		InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
		Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
		if (cmdChannel == null) {

			// 该端口还没有代理客户端
			ctx.channel().close();
		} else {

			// 用户连接断开，从控制连接中移除
			String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
			ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
			Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
			if (proxyChannel != null && proxyChannel.isActive()) {
				proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
				proxyChannel.attr(Constants.TOKEN).remove();
				proxyChannel.attr(Constants.USER_ID).remove();

				proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
				// 通知客户端，用户连接已经断开
				ProxyMessage proxyMessage = new ProxyMessage();
				proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
				proxyMessage.setUri(userId);
				proxyChannel.writeAndFlush(proxyMessage);
			}
		}

		super.channelInactive(ctx);
	}

	@Override
	public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
		// 通知代理客户端
		Channel userChannel = ctx.channel();
		InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
		Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
		if (cmdChannel == null) {
			// 该端口还没有代理客户端
			ctx.channel().close();
		} else {
			Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
			if (proxyChannel != null) {
				proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
			}
		}

		super.channelWritabilityChanged(ctx);
	}

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof TransferEvent) {
			FullHttpRequest request = ctx.channel().attr(Constants.USER_REQUEST_DATA).get();
			httpRequestHandler(ctx, request);
		} else {
			ctx.fireUserEventTriggered(evt);
		}
	}

	/**
	 * 通知客户端发起连接
	 */
	public void connect(ChannelHandlerContext ctx, String host) {
		// 用户通道
		Channel userChannel = ctx.channel();
		InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
		int port = sa.getPort();
		String scheme = "http";
		if (port == 1574) {
			scheme = "https";
		}
		String token = service.getTokenByDomain(host);
		// 客户端通道
		Channel cmdChannel = ProxyChannelManager.getCmdChannel(token);

		if (cmdChannel == null) {

			// 该端口还没有代理客户端
			ctx.channel().close();
		} else {
			String userId = newUserId();
			// 增加用户连接与代理客户端连接关系
			ProxyChannelManager.addUserChannelToCmdChannel(cmdChannel, userId, userChannel);
			// 构建协议消息
			ProxyMessage proxyMessage = new ProxyMessage();
			proxyMessage.setType(ProxyMessage.TYPE_CONNECT);
			proxyMessage.setUri(userId);
			String domain = scheme + "://" + host;
			proxyMessage.setData(domain.getBytes());
			// 通知客户端发起连接请求
			cmdChannel.writeAndFlush(proxyMessage);
		}
	}

	/**
	 * 为用户连接产生ID
	 *
	 * @return
	 */
	private static String newUserId() {
		return String.valueOf(userIdProducer.incrementAndGet());
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		// 当出现异常就关闭连接
		ctx.close();
	}
}